//: [Previous](@previous)
import Foundation
import UIKit
//: ## 属性
/**
 swift中跟实例相关的属性,可以分为两种
 * 存储属性(Stored Property)
  - 类似于成员变量这个概念
  - 存储在实例的内存中
  - 结构体 / 类 可以定义存储属性
  - 枚举 不可以 定义存储属性

 * 计算属性(Computed Property)
  - 本质就是方法(函数)
  - 不占用实例的内存
  - 枚举 / 类 / 结构体 都可以定义计算属性
 */

//Circle圆,radius半径,直径. 圆的半径 = 直径的一半
struct Circle {
    //存储属性
    var radius: Double
    //计算属性
    var diameter: Double {
        set {
            radius = newValue / 2 //新赋的值会默认为newValue
        }
//        set(newDiameter) {
//            radius = newDiameter / 2
//        } 和上面写法一样的效果
        get {
            radius * 2
        }
    }
}
var circle = Circle(radius: 10)
print(circle.radius)
circle.radius = 20
circle.diameter = 30
print(circle.radius)
//: ## 存储属性
/**
 关于存储属性,swift有个明确的规定
  1. 在创建 类 或 结构体 的实例时,必须为所有存储属性设置一个合适的初始值
   1.1 可以在初始化器里为存储属性设置一个初始值
    struct p1 {
        var a: Int
        var b: Int
        init(a: Int, b: Int) {
         self.a = 10
         self.b = 20
        }
    }
   1.2 可以分配一个默认的属性值作为属性定义的一部分
    struct p1 {
        var a: Int = 10
        var b: Int = 20
    }
 */
struct p1 {
    var a: Int
    var b: Int
}
//1.var pp = p1 就会报错,因为pp实例里面的存储属性这样子是没有初始值的
//var pp = p1()
//: ## 计算属性
/**
 1.set 传入的新值默认叫做 newValue, 也可以自定义
 struct Circle {
         //计算属性
         var diameter: Double {
            set(newDiameter) {
            radius = newDiameter / 2
            }
             get {
                 radius * 2
             }
         }
        }
 
 2.只读计算属性 : 只有get, 没有set[可以有get没有set,但是不能有set没有get]
 struct Circle {
     //计算属性
     var diameter: Double {
         get {
             radius * 2
         }
     }
 }
 //简单的写法,因为diameter后面只有get,所以可以简写直接大括号{radius * 2}
 struct Circle {
     //计算属性
     var diameter: Double {
        radius * 2
     }
 }
 
 3.定义计算属性只能用var,不能用let,因为计算属性的值是可能发生变化的(即使是只读计算属性也是一样)
 */
//: ## 枚举rawValue的原理
// 枚举值原始值rawValue的本质是: 只读计算属性
enum TestEnum: Int {
    case test1 = 1, test2 = 2, test3 = 3
    
    var rawValue: Int {
        switch self {
        case .test1:
            return 11
        case .test2:
            return 22
        case .test3:
            return 33
        }
    }
}
var testEnum = TestEnum.test2
print(testEnum.rawValue) //22 本来的rawValue应该是2的,但是利用只读计算属性的原理改了
//: ## 延迟存储属性[*重点] (Lazy Stored Property)(类似懒加载)
// 使用lazy 可以定义一个延迟存储属性, 在第一次用到此属性的时候才会被初始化
// lazy属性必须是用var, 不能是let, 因为let必须在实例的初始化完菜之前就拥有值
// lazy弊端: 如果多线程同时第一次访问lazy属性, 无法保住属性只被初始化一次, 多个线程都调用, 有可能多次被初始化, 会出现数据错误可能
class Car {
    init() {
        print("Car Init")
    }
    func run(){
        print("Car is run")
    }
}

class Person1 {
    var car = Car()
    
    init() {
        print("Person Init")
    }
    
    func goOut(){
        car.run()
    }
}

var per1 = Person1()
print("-------")
per1.goOut()
/**
 Car Init
 Person Init
 -------
 Car is run
 */

class Person2 {
    lazy var car = Car()
    
    init() {
        print("Person Init")
    }
    
    func goOut(){
        car.run()
    }
}
var per2 = Person2()
print("-------")
per2.goOut()
/**
 Person Init
 -------
 Car Init
 Car is run
 */
// 从上面2次per的输出顺序结果可以看出,在person类里面创建的var car 是一个存储属性,存储属性必须有初始值,所以在class person创建的时候,里面的属性也必须创建了.但是加上了lazy去修饰car后,就可以避免这个情况,哪怕是必须要有初始值的car也可以在未使用的情况下先不创建初始化.在person1里面,car是在init之前就已经初始化了.但是在person2里面,car是在init之后,再到调用了goOut方法了才被初始化

//class PhotoView {
//    lazy var image: Image = {
//        let url = "http:abc.png"
//        let data = Data(url: url)
//        return Image(data: data)
//    }()
//}
// image后面用闭包表达式,在闭包表达式里面网络请求加载成功了,return图片,才给本身赋值,就可以避免加载不成功也要浪费内存去初始化
//: ## 延迟存储属性注意点
// 当结构体包含一个延迟存储属性时, 只有var才能访问,因为延迟存储属性初始化时需要改变了结构体的内存
struct Point1 {
    var x = 0
    var y = 0
    lazy var z = 0
}
let p11 = Point1() //此时p11已经初始化完成, let的内存不允许改变, 结构体是值类型, 哪怕z没用到是空值也已经定了
//p11.z //现在调用z, 就会z被赋值0, 这个时候结构体的内存会发生改变, 就互相冲突了

class Point2 {
    var x = 0
    var y = 0
    lazy var z = 0
}
let p111 = Point2()
print(p111.z) //正常输出0, 因为class是引用类型, 内存只是存地址值, 改变的是z的内容, 没有改变z的地址值
//: ## 属性观察器 (Proper Observer)(OC的KVO)
// 可以为 非lszy  var修饰的 存储属性 设置属性观察器(3个条件缺一不可)
// willSet newValue即将改变的值
// didSet oldValue旧的值,var XX,这个XX就是新的值
// 在初始化器钟设置属性值,不会触发willSet和didSet. 在属性定义的时候设置初始值也不会触发属性观察器,因为在属性定义的时候直接设置初始值相当于Init
struct Circle1 {
    var radius: Double {
        willSet {
            print("willSet", newValue)
        }
        didSet {
            print("didSet", oldValue, radius)
        }
    }
    
    var x: Double {
        set {
            print("计算属性x的set方法,新的值是",newValue)
        }
        get {
            return radius * 2
        }
    }
    
    init(radius: Double) {
        //在初始化器钟设置属性值,不会触发willSet和didSet
        self.radius = 5
        print("init")
    }
}
var C1 = Circle1(radius: 10) //"init\n"
C1.radius = 20
/**
 "willSet 20.0\n"
 "didSet 10.0 20.0\n"
 */
print(C1.x)
C1.x = 2 // 计算属性x的set方法,新的值是 2.0
// 关于为什么计算属性不能设置属性观察器,计算属性有自己的set方法了,可以直接在set方法里面写代码了,不需要多搞个willset
//: ## 全局变量, 局部变量
// 计算属性的功能 / 属性观察器, 同样可以应用在全局变量 / 局部变量 身上
var num: Int {
    get {
        return 10
    }
    
    set {
        print("setNum",newValue)
    }
}
num = 11 //"setNum 11\n"
print(num) // "10\n"

func test1() {
    var age = 10 {
        willSet {
            print("willSet", newValue)
        }
        didSet {
            print("didSet", oldValue, age)
        }
    }
    age = 11//willSet 11
            //didSet 10 11
}
test1()
//: ## inout的本质总结
/**
 1.如果实参有物理内存地址,且没有设置属性观察器
  - 直接将实参的内存地址传入函数(实参进行引用传递)
 2.如果实参是计算属性 或者 设置了属性观察器 (会采用Copy in Copy out的做法, 就是复制出来到副本上(在局部变量新申请个内存),然后修改副本的值,再把副本修改后的值,赋值回去给一开始的值)
  - 调用该函数时, 先复制实参的值, 产生副本[get方法]
  - 将副本的内存地址传入函数(副本进行引用传递), 在函数内部可以修改副本的值
  - 函数返回后, 再将副本的值覆盖实参的值[set方法]
  
 总结:inout的本质就是引用传递(地址传递)
 */
//: ## 类型属性(Type Property)
/**
 严格来说,属性可以分为2种
 1. 实例属性(Instance Property) : 只能通过实例去访问
  1.1 存储实例属性(Stored Instance Property) : 存储在实例的内存中, 每个实例都有1份
  1.2 计算实例属性(Computed Instance Property)
 
 2. 类型属性(type Property) : 只能通过类型去访问
  2.1 存储类型属性(Stored Type Property) : 整个程序运行过程中, 就只有1份内存(类似全局变量)
  2.2 计算类型属性(Computed Type Property)
  结构体和类可以通过static定义类型属性, 如果是类也可以用关键字class
 */
struct PP {
    var A: Int = 0 // 存储实例属性
    var B: Int { // 计算实例属性
        set {
        }
        get {
            return A * 2
        }
    }
    static var C: Int = 0 // 存储类型属性
    static var D: Int { // 计算类型属性
        set {
        }
        get {
            return C * 2
        }
    }
}
var PPP = PP()
//实例属性 和 类型属性 的区别还在于调用, 实例属性是实例调用, 类型属性是类型自己调用
PPP.A //可以
PPP.B //可以
//PPP.C //不可以
//PPP.D //不可以
PP.C //可以
PP.D //可以

struct Car1 {
    static var carNum: Int = 0
    init() {
        Car1.carNum += 1
    }
}
let c1 = Car1()
let c2 = Car1()
let c3 = Car1() //3次创建,就有3次init,因为carNum是计算类型属性,只有一份地址,所以3次+=1都是对同一个地址的值操作
print(Car1.carNum)
//: ## 类型属性细节
/**
 1.不同于存储实例对象, 你必须给存储实例对象设定初始值(因为类型对象没有像实例对象一直的init初始化器来初始化存储属性)
 struct dif {
     var a: Int = 10
     static var b: Int
 }
 
 2.存储类型属性默认就是lazy, 会在第一次使用的时候才初始化
   就算被多个线程同时访问, 也会保证只会初始化一次 (和存储实例对象+lazy要区分开,实例对象+lazy是无法保证被多个线程同时访问,只初始化一次)
 
 3.存储类型属性,可以用let. 虽然存储类型属性默认使用了lazy, 但是因为存储类型属性没有初始化器, 也就不会违反let的原则(初始化之前必须有值), 这样子lazy延迟初始化和let就不会冲突
 
 4.枚举类型,也可以定义类型属性(存储类型属性 计算类型属性), 枚举是不允许存放存储实例属性的, 因为枚举的内存只允许存放case和关联值, 实例属性的内存要放到对象内存中, 所以枚举不行. 但是存储类型属性的内存并不会放到实例对象中, 所以可以
 */

// ## 单利模式 (类型属性的经典应用)
class FileManager { // 假设需要一个仅此一份的文件管理器
    private init() { //因为init方法加上了private,所以不允许外部使用,想要使用,就只能自己内部使用
    }
    
    public static let shared = FileManager() //相当于用一个属性来运行初始化器.这个是类型存储属性(static), 即使类型存储属性默认使用了lazy, 但是因为类型存储属性没有初始化器, 所以还是可以使用let. 就变成了需要使用的时候才会执行(lazy), 并且是独有一份的内存(let), 就形成了经典的单利模式
    
    func open() { //假设还有个方法
        
    }
}
//var a = FileManager() 会报错,因为初始化器被私有化
var fi = FileManager.shared //初始化使用
FileManager.shared.open() //调用方法












//: [Next](@next)
